//
//  HandwritingOpencv.m
//  handwritingprocessing
//
//  Created by Wdc on 2020/7/2.
//

#import "HandwritingOpencv.h"

#import <UIKit/UIKit.h>

#define PI 3.1415926

using namespace std;
using namespace cv;

@implementation HandwritingOpencv

int _blackWriteVal=0;
int _removalRedVal=0;
int _removalBlueVal=0;

//修补大小
int _patchSize=0;

#pragma mark Public
+ (UIImage *)handwriting:(UIImage *)input type:(int)type blackWriteVal:(int)blackWriteVal removalRedVal:(int)removalRedVal removalBlueVal:(int)removalBlueVal patchSize:(int)patchSize {
    _blackWriteVal=blackWriteVal;
    _removalRedVal=removalRedVal;
    _removalBlueVal=removalBlueVal;
    _patchSize=patchSize;
    
    Mat inputMat = [HandwritingOpencv _matFrom:input];
    Mat outMat;
    bool isBlackWrite= (type & 1) == 1;
    if(type == 1){
    outMat = [HandwritingOpencv imageBlackWrite:inputMat];
    }else if ((type>>1 & 3) == 1){
    outMat = [HandwritingOpencv removalRed:inputMat isBlackWrite:isBlackWrite];
    }else if ((type>>1 & 3) == 2){
    outMat = [HandwritingOpencv removalBlue:inputMat isBlackWrite:isBlackWrite];
    }else if ((type>>1 & 3) == 3){
    outMat = [HandwritingOpencv removalRedAndBlue:inputMat isBlackWrite:isBlackWrite];
    }
    return  [HandwritingOpencv _imageFrom:outMat];
}
    
#pragma mark Private
    
+ (Mat)_matFrom:(UIImage *)source {
    CGImageRef imageRef = CGImageCreateCopy(source.CGImage);
    CGFloat cols = CGImageGetWidth(imageRef);
    CGFloat rows = CGImageGetHeight(imageRef);
    Mat result(rows, cols, CV_8UC4);
    
    CGBitmapInfo bitmapFlags = kCGImageAlphaNoneSkipLast | kCGBitmapByteOrderDefault;
    size_t bitsPerComponent = 8;
    size_t bytesPerRow = result.step[0];
    CGColorSpaceRef colorSpace = CGImageGetColorSpace(imageRef);
    
    CGContextRef context = CGBitmapContextCreate(result.data, cols, rows, bitsPerComponent, bytesPerRow, colorSpace, bitmapFlags);
    CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, cols, rows), imageRef);
    CGContextRelease(context);
    CGImageRelease(imageRef);

    return result;
}
    
+ (UIImage *)_imageFrom:(Mat)source {
    
    NSData *data = [NSData dataWithBytes:source.data length:source.elemSize() * source.total()];
    CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
    
    CGBitmapInfo bitmapFlags = kCGImageAlphaNone | kCGBitmapByteOrderDefault;
    size_t bitsPerComponent = 8;
    size_t bytesPerRow = source.step[0];
    CGColorSpaceRef colorSpace = (source.elemSize() == 1 ? CGColorSpaceCreateDeviceGray() : CGColorSpaceCreateDeviceRGB());
    
    CGImageRef image = CGImageCreate(source.cols, source.rows, bitsPerComponent, bitsPerComponent * source.elemSize(), bytesPerRow, colorSpace, bitmapFlags, provider, NULL, false, kCGRenderingIntentDefault);
    UIImage *result = [UIImage imageWithCGImage:image];
    
    CGImageRelease(image);
    CGDataProviderRelease(provider);
    CGColorSpaceRelease(colorSpace);
    
    return result;
}

+ (void)exThreshold:(cv::Mat &)out_base srcGray:(const cv::Mat &)srcGray {
    const int maxVal = 255;
    int cols=srcGray.cols/20;
    int blockSize = cols%2==0?cols+1:cols;    //取值3、5、7....等
    int constValue = _blackWriteVal;
    int adaptiveMethod = 1;
    int thresholdType = 0;
    /*
     阈值算法
     0:ADAPTIVE_THRESH_MEAN_C
     1:ADAPTIVE_THRESH_GAUSSIAN_C
     --------------------------------------
     阈值类型
     0:THRESH_BINARY
     1:THRESH_BINARY_INV
     */
    if(_blackWriteVal==1000){
        threshold(srcGray,out_base, 0, maxVal, THRESH_BINARY + THRESH_OTSU);
    }else{
        adaptiveThreshold(srcGray, out_base, maxVal, adaptiveMethod, thresholdType, blockSize, constValue);
    }
}

+ (Mat)imageBlackWrite:(Mat)src_base{
    Mat out_base;
    
    Mat srcGray;
    cvtColor(src_base, srcGray, COLOR_BGR2GRAY);
    
    [self exThreshold:out_base srcGray:srcGray];
    
    return out_base;
}

+ (void)exMorphologyEx:(cv::Mat &)dst outMat:(const cv::Mat &)outMat {
    if (_patchSize > 0) {
        Mat dstOutTemp;
        bitwise_or(dst, outMat, dstOutTemp);
        
        Mat element = getStructuringElement(MORPH_RECT, cv::Size(_patchSize, _patchSize));
        morphologyEx(dstOutTemp, dstOutTemp, MORPH_RECT, element);//腐蚀操作
        
        bitwise_and(dst, dstOutTemp, dst);
    }
}

+ (void)extracted:(cv::Mat &)dst isBlackWrite:(bool)isBlackWrite isWriteBg:(bool)isWriteBg out_base:(cv::Mat &)out_base src_base:(const cv::Mat &)src_base {
    if (isBlackWrite) {
        Mat outMat=[HandwritingOpencv imageBlackWrite:src_base];
        [self exMorphologyEx:dst outMat:outMat];
        bitwise_or(dst, outMat, out_base);
    } else {
        if(isWriteBg){
            cvtColor(dst, dst, COLOR_GRAY2BGRA);
            bitwise_or(dst, src_base, out_base);
        }else {
            Mat tempMat;
            cvtColor(src_base, tempMat, COLOR_BGRA2BGR);
            inpaint(tempMat, dst, out_base, 0, INPAINT_TELEA);
        }
    }
}

+ (Mat)removalRed:(Mat)src_base isBlackWrite:(bool)isBlackWrite{
     Mat out_base ;
     
     Mat srcHsv;
//     cvtColor(src_base, srcHsv, COLOR_BGR2HSV);
     
     vector<Mat> channels;
     Mat channels_m[3];
     Mat channels_sub[2];
     Mat dst;

     split(src_base, channels);
     channels_m[0] = channels[0];  // b
     channels_m[1] = channels[1];
     channels_m[2] = channels[2];
     
     subtract(channels_m[0],channels_m[1],channels_sub[0]);
     subtract(channels_m[0],channels_m[2],channels_sub[1]);

     threshold(channels_sub[0], channels_sub[0], _removalRedVal, 255, 0);
     threshold(channels_sub[1], channels_sub[1], _removalRedVal, 255, 0);
     bitwise_and(channels_sub[0], channels_sub[1], dst);
    
    bool isWriteBg=_removalRedVal==20;
    [self extracted:dst isBlackWrite:isBlackWrite isWriteBg:true out_base:out_base src_base:src_base];
    return out_base;
 }

// + (Mat)removalRed:(Mat)src_base{
//     Mat out_base = src_base;
//
//    int width = src_base.cols;
//	int height = src_base.rows;
//
//	int x, y;
//	double B = 0.0, G = 0.0, R = 0.0, H = 0.0, S = 0.0, V = 0.0;
//	Mat vec_rgb = Mat::zeros(src_base.size(), CV_8UC1);
//
//	for (x = 0; x < height; x++)
//	{
//		for (y = 0; y < width; y++)
//		{
//			B = src_base.at<Vec3b>(x, y)[0];
//			G = src_base.at<Vec3b>(x, y)[1];
//			R = src_base.at<Vec3b>(x, y)[2];
//			RGB2HSV(R, G, B, H, S, V);
//			//红色范围，范围参考的网上。可以自己调
//			if ((H >=0 && H <= 10 || H >=156 && H <= 180) && (S >= 0  && S <= 255) && (V>0  && V < 255))
//			{
//				out_base.at<Vec3b>(x, y)[0] = 255;
//				out_base.at<Vec3b>(x, y)[1] = 255;
//				out_base.at<Vec3b>(x, y)[2] = 255;
//			}
//			else{
//				out_base.at<Vec3b>(x, y)[0] = src_base.at<Vec3b>(x, y)[0];
//				out_base.at<Vec3b>(x, y)[1] = src_base.at<Vec3b>(x, y)[1] ;
//				out_base.at<Vec3b>(x, y)[2] = src_base.at<Vec3b>(x, y)[2] ;
//			}
//
//		}
//	}
//
//     return out_base;
// }

  + (Mat)removalBlue:(Mat)src_base isBlackWrite:(bool)isBlackWrite{
      Mat out_base;
      
      Mat srcHsv;
      
      vector<Mat> channels;
      Mat channels_m[3];
      Mat channels_sub[2];
      Mat dst;

      split(src_base, channels);
      channels_m[0] = channels[0];  // b
      channels_m[1] = channels[1];
      channels_m[2] = channels[2];
      
      subtract(channels_m[2],channels_m[0],channels_sub[0]);
      subtract(channels_m[2],channels_m[1],channels_sub[1]);
      
//      blur(channels_sub[0], channels_sub[0], Size(3,3) );

      threshold(channels_sub[0], channels_sub[0], _removalBlueVal, 255, 0);
      threshold(channels_sub[1], channels_sub[1], _removalBlueVal, 255, 0);
      bitwise_and(channels_sub[0], channels_sub[1], dst);

      bool isWriteBg=_removalBlueVal==5;
      [self extracted:dst isBlackWrite:isBlackWrite isWriteBg:true out_base:out_base src_base:src_base];
      return out_base;
  }

  + (Mat)removalRedAndBlue:(Mat)src_base isBlackWrite:(bool)isBlackWrite{
      Mat out_base;
      
      Mat srcHsv;
      
      vector<Mat> channels;
      Mat channels_m[3];
      Mat channels_sub[2];
      Mat dst;

      split(src_base, channels);
      channels_m[0] = channels[0];  // b
      channels_m[1] = channels[1];
      channels_m[2] = channels[2];
      
      subtract(channels_m[2],channels_m[0],channels_sub[0]);
      subtract(channels_m[2],channels_m[1],channels_sub[1]);
      
//      blur(channels_sub[0], channels_sub[0], Size(3,3) );

      threshold(channels_sub[0], channels_sub[0], _removalBlueVal, 255, 0);
      threshold(channels_sub[1], channels_sub[1], _removalBlueVal, 255, 0);
      bitwise_and(channels_sub[0], channels_sub[1], dst);
      
      //red
      Mat dstRed;
      Mat channels_subRed[2];
      subtract(channels_m[0],channels_m[1],channels_subRed[0]);
      subtract(channels_m[0],channels_m[2],channels_subRed[1]);

      threshold(channels_subRed[0], channels_subRed[0], _removalRedVal, 255, 0);
      threshold(channels_subRed[1], channels_subRed[1], _removalRedVal, 255, 0);
      bitwise_and(channels_subRed[0], channels_subRed[1], dstRed);
      bitwise_or(dst, dstRed, dst);

      bool isWriteBg=_removalRedVal==20&&_removalBlueVal==5;
      [self extracted:dst isBlackWrite:isBlackWrite isWriteBg:true out_base:out_base src_base:src_base];
      
      return out_base;
  }

+ (float)imageCompare:(Mat) src_base src_test1:(Mat) src_test1 {
    Mat hsv_base;
    Mat hsv_test1;
    
    /// Convert to HSV
    cvtColor( src_base, hsv_base, COLOR_BGR2HSV );
    cvtColor( src_test1, hsv_test1, COLOR_BGR2HSV );
    
    /// Using 50 bins for hue and 60 for saturation
    int h_bins = 50; int s_bins = 60;
    int histSize[] = { h_bins, s_bins };
    
    // hue varies from 0 to 179, saturation from 0 to 255
    float h_ranges[] = { 0, 180 };
    float s_ranges[] = { 0, 256 };
    
    const float* ranges[] = { h_ranges, s_ranges };
    
    // Use the o-th and 1-st channels
    int channels[] = { 0, 1 };
    
    /// Histograms
    MatND hist_base;
    MatND hist_half_down;
    MatND hist_test1;
    MatND hist_test2;
    
    /// Calculate the histograms for the HSV images
    calcHist( &hsv_base, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false );
    normalize( hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat() );
    
    calcHist( &hsv_test1, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false );
    normalize( hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat() );
    
    float bestBHATTACHARYYA = compareHist( hist_base, hist_test1, 3 ); // [0: 1] 0 is best
    float baseCORREL = compareHist( hist_base, hist_test1, 0 ); // [0: 1] 1 is best
    
    float calculated = ((1 - bestBHATTACHARYYA) + baseCORREL) / 2;
    return calculated;
}

void RGB2HSV(double red, double green, double blue, double& hue, double& saturation, double& intensity)
{

	double r, g, b;
	double h, s, i;

	double sum;
	double minRGB, maxRGB;
	double theta;

	r = red / 255.0;
	g = green / 255.0;
	b = blue / 255.0;

	minRGB = ((r<g) ? (r) : (g));
	minRGB = (minRGB<b) ? (minRGB) : (b);

	maxRGB = ((r>g) ? (r) : (g));
	maxRGB = (maxRGB>b) ? (maxRGB) : (b);

	sum = r + g + b;
	i = sum / 3.0;

	if (i<0.001 || maxRGB - minRGB<0.001)
	{
		h = 0.0;
		s = 0.0;
	}
	else
	{
		s = 1.0 - 3.0*minRGB / sum;
		theta = sqrt((r - g)*(r - g) + (r - b)*(g - b));
		theta = acos((r - g + r - b)*0.5 / theta);
		if (b <= g)
			h = theta;
		else
			h = 2 * PI - theta;
		if (s <= 0.01)
			h = 0;
	}

	hue = (int)(h * 180 / PI);
	saturation = (int)(s * 100);
	intensity = (int)(i * 100);
}
@end
